#include "stdafx.h"
#include "PhoneUtils.h"

#include <windows.h>
#include <regext.h>
#include <tapi.h>
#include <tsp.h>
#include <snapi.h>

#include "Settings.h"

#ifdef _DEBUG
#include "crtdbg.h"
#endif

#define MAX_COMMAND_LINE 512

#define DEFAULT_SMS_APPLICATION         TEXT("tmail.exe")
#define DEFAULT_SMS_APPLICATION_ARGS    TEXT("-service \"SMS\" -to \"%s\"")
#define DEFAULT_MAIL_APPLICATION        TEXT("tmail.exe")
#define DEFAULT_MAIL_APPLICATION_ARGS   TEXT("-service \"%s\" -to \"%s\"")
#define DEFAULT_DIALER_APPLICATION      TEXT("cprog.exe")
#define DEFAULT_DIALER_APPLICATION_ARGS TEXT("")
#define OUTLOOK_APPLICATION             TEXT("\\windows\\poutlook.exe")
#define OUTLOOK_APPLICATION_ARGS        TEXT("Contacts")

#ifdef CALLVMAIL

#define EXIT_ON_NULL(_p) if (_p == NULL) { hr = E_OUTOFMEMORY; goto FuncExit; }
#define EXIT_ON_FALSE(_f) if (!(_f)) { hr = E_FAIL; goto FuncExit; }

#define TAPI_API_LOW_VERSION   0x00020000
#define TAPI_API_HIGH_VERSION  0x00020000

#define CAPS_BUFFER_SIZE    512

#define MAX_REG_SZ_CHARS 50

// function prototypes
HRESULT SHGetPhoneNumber(LPTSTR, UINT, UINT);
HRESULT SHReadLineAddressCaps(LPTSTR, UINT, PDWORD, UINT);

#endif

#include <windows.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

#define EXT_API_LOW_VERSION     0x00010000
#define EXT_API_HIGH_VERSION    0x00010000

void FAR PASCAL LineCallback(DWORD hDevice,
               DWORD dwMsg,
               DWORD dwCallbackInstance,
               DWORD dwParam1,
               DWORD dwParam2,
               DWORD dwParam3)
{
  switch(dwMsg)
  {
  case LINE_DEVSPECIFIC:
    {
      if(dwParam1 == LINE_USSD)
      {
        DWORD  dwMessageId  = dwParam2;
      }

      break;
    }
  case LINE_REPLY:
    {
      if(dwParam2 == 0) return;
      wprintf(L"Error: ");  
      switch(dwParam2)
      {
      case LINEERR_INVALLINEHANDLE:    wprintf(L"LINEERR_INVALLINEHANDLE");  break;
      case LINEERR_NOMEM:          wprintf(L"LINEERR_NOMEM");        break;
      case LINEERR_OPERATIONUNAVAIL:    wprintf(L"LINEERR_OPERATIONUNAVAIL");  break;
      case LINEERR_OPERATIONFAILED:    wprintf(L"LINEERR_OPERATIONFAILED");  break;
      case LINEERR_RESOURCEUNAVAIL:    wprintf(L"LINEERR_RESOURCEUNAVAIL");  break;
      case LINEERR_INVALPOINTER:      wprintf(L"LINEERR_INVALPOINTER");    break;
      case LINEERR_INVALPARAM:      wprintf(L"LINEERR_INVALPARAM");      break;
      case LINEERR_UNINITIALIZED:      wprintf(L"LINEERR_UNINITIALIZED");    break;
      default:              wprintf(L"Error: %x",dwParam2);      break;
      }
      wprintf(L"\r\n");
      break;
    }
  }
}

long InitializeTAPI(HLINEAPP & lineApp, DWORD & lowAPIVersion, DWORD & deviceCount)
{
  LINEINITIALIZEEXPARAMS  lineParams;
  ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS));

  lineParams.dwTotalSize  = sizeof(LINEINITIALIZEEXPARAMS);
  lineParams.dwOptions  = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 

  lowAPIVersion = TAPI_CURRENT_VERSION;

  return lineInitializeEx(&lineApp,
    (HINSTANCE)GetModuleHandle(NULL),
    LineCallback,
    L"wxUSSDRequest",
    &deviceCount,
    &lowAPIVersion,
    &lineParams);
}

long GetCellularLineID(HLINEAPP lineApp,
             DWORD lowAPIVersion,
             DWORD deviceCount,
             DWORD & apiVersion)
{
  DWORD        dwReturn    = 0xFFFFFFFF;
  long        lResult      = 0;
  LINEEXTENSIONID    sLineExt    = {0};
  LPLINEDEVCAPS    lpLineDevCaps  = NULL;  
  BOOL        bContinue    = TRUE;

  for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine)
  {
    lResult    = lineNegotiateAPIVersion(lineApp,
      dwLine,
      lowAPIVersion,
      TAPI_CURRENT_VERSION,
      &apiVersion,
      &sLineExt);

    if(0 == lResult)
    {
      lpLineDevCaps  = (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS));
      lResult      = LINEERR_STRUCTURETOOSMALL;

      lpLineDevCaps->dwTotalSize  = sizeof(LINEDEVCAPS);
      lpLineDevCaps->dwNeededSize  = sizeof(LINEDEVCAPS);

      while(LINEERR_STRUCTURETOOSMALL == lResult)
      {
        lResult  = lineGetDevCaps(lineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps);

        if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize)
        {
          lpLineDevCaps  = (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE);
          lResult      = LINEERR_STRUCTURETOOSMALL;

          lpLineDevCaps->dwTotalSize  = lpLineDevCaps->dwNeededSize;
        }
      }

      if(0 == lResult)
      {
        TCHAR szName[512];

        memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), 
          lpLineDevCaps->dwLineNameSize);

        szName[lpLineDevCaps->dwLineNameSize]  = 0;

        if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0)
        {
          dwReturn  = dwLine;
          bContinue  = FALSE;
        }
      }

      LocalFree((HLOCAL)lpLineDevCaps);
    }
  }

  return dwReturn;
}

HLINE OpenTAPILine(HLINEAPP lineApp, DWORD cellularID, DWORD apiVersion)
{
  DWORD  dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
  HLINE  hLine    = NULL;
  DWORD  extVersion  = 0;
  long  lReturn    = lineOpen(lineApp, 
    cellularID, 
    &hLine,
    TAPI_CURRENT_VERSION, 0,
    (DWORD)NULL,
    LINECALLPRIVILEGE_OWNER,
    dwMediaMode, 0);

  lReturn = ::lineNegotiateExtVersion(lineApp,
    cellularID,
    apiVersion, 
    EXT_API_LOW_VERSION, 
    EXT_API_HIGH_VERSION, 
    &extVersion);

  return hLine;
}

void ShutdownTAPI(HLINE cellularLine, HLINEAPP lineApp)
{
  if (cellularLine)
    lineClose(cellularLine);
  if (lineApp)
    lineShutdown(lineApp);
}


bool IsUSSDCommand(const TCHAR * number)
{
  return ((_tcslen(number) > 2) && (*number == TEXT('*')));
}

int PhoneUtils::SendUSSD(const TCHAR * command)
{
  HLINEAPP lineApp = NULL;
  DWORD lowAPIVersion = 0;
  DWORD apiVersion = 0;
  DWORD deviceCount = 0;
  DWORD cellularID = 0;
  HLINE cellularLine = NULL;
  int errorCode;

  do
  {
    if(InitializeTAPI(lineApp, lowAPIVersion, deviceCount) != 0) 
    {
      errorCode = 1;
      break;
    }
    cellularID = GetCellularLineID(lineApp, lowAPIVersion, deviceCount, apiVersion);
    if(cellularID == 0xFFFFFFFF)
    {
      errorCode = 2;
      break;
    }
    cellularLine = OpenTAPILine(lineApp, cellularID, apiVersion);
    if(cellularLine == NULL)
    {
      errorCode = 3;
      break;
    }
    if(lineSendUSSD(cellularLine, (const BYTE* const)command, _tcslen(command) * sizeof(TCHAR), 0) < 0)
    {
      errorCode = 4;
      break;
    }
    return 0;
  }
  while(false);
  ShutdownTAPI(cellularLine, lineApp);
  return errorCode;
}


// main functions

void PhoneUtils::Call(const TCHAR * number, const TCHAR * name)
{
  TCHAR szTemp[256];
  PHONEMAKECALLINFO mci = {0};

  if (Settings::enableUSSDStarDialing && IsUSSDCommand(number))
  {
    SendUSSD(number);
    return;
  }

  mci.cbSize = sizeof(mci);
  mci.dwFlags = 0;
  mci.pszCalledParty = name;
  mci.pszDestAddress = number;
  if (Settings::phonePrefix != NULL && Settings::phonePrefix[0] != 0)
  {
    if (_tcsncmp(number, Settings::phonePrefix, _tcslen(Settings::phonePrefix)) != 0)
    {
      StringCchCopy(szTemp, 256, Settings::phonePrefix);
      StringCchCat(szTemp,  256, number);
      mci.pszDestAddress = szTemp;
    }
  }
  PhoneMakeCall(&mci);
}

void PhoneUtils::SendSMS(const TCHAR * to)
{
    PROCESS_INFORMATION pi;
    TCHAR tszCommandLine[MAX_COMMAND_LINE];

    const TCHAR* app = DEFAULT_SMS_APPLICATION;
    if (Settings::smsApplication && Settings::smsApplication[0] != 0)
      app = Settings::smsApplication;

    const TCHAR* fmtStr = DEFAULT_SMS_APPLICATION_ARGS;
    if (Settings::smsApplicationArgs && Settings::smsApplicationArgs[0] != 0)
      fmtStr = Settings::smsApplicationArgs;

    StringCchPrintf(tszCommandLine, MAX_COMMAND_LINE, fmtStr, to);

    BOOL res = CreateProcess(app, tszCommandLine, 
        NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);

    if (!res && app == Settings::smsApplication)
    {
      StringCchPrintf(tszCommandLine, MAX_COMMAND_LINE, DEFAULT_SMS_APPLICATION_ARGS, to);
      CreateProcess(DEFAULT_SMS_APPLICATION, tszCommandLine, 
        NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
    }
}

void PhoneUtils::SendEMail(const TCHAR * account, const TCHAR * to)
{
  PROCESS_INFORMATION pi;
  TCHAR tszCommandLine[MAX_COMMAND_LINE];

  const TCHAR* app = DEFAULT_MAIL_APPLICATION;
  if (Settings::mailApplication && Settings::mailApplication[0] != 0)
    app = Settings::mailApplication;

  const TCHAR* fmtStr = DEFAULT_MAIL_APPLICATION_ARGS;
  if (Settings::mailApplicationArgs && Settings::mailApplicationArgs[0] != 0)
    fmtStr = Settings::mailApplicationArgs;

  // If account == "", then get the last used email account name from the phone
  if (_tcslen(account) > 0)
  {
    StringCchPrintf(tszCommandLine, MAX_COMMAND_LINE, fmtStr, account, to);
  }
  else
  {
    // This should be the name of the last used email account
    TCHAR tszAccountName[64] = {0};
    HRESULT hr = RegistryGetString(
        SN_MESSAGINGLASTEMAILACCOUNTNAME_ROOT, 
        SN_MESSAGINGLASTEMAILACCOUNTNAME_PATH, 
        SN_MESSAGINGLASTEMAILACCOUNTNAME_VALUE, 
        tszAccountName, 64);
    if (FAILED(hr) || tszAccountName[0] == 0)
      StringCchCopy(tszAccountName, MAX_COMMAND_LINE, TEXT("default"));

    StringCchPrintf(tszCommandLine, MAX_COMMAND_LINE, fmtStr, tszAccountName, to);
  }

  BOOL res = CreateProcess(app, tszCommandLine, 
      NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
  if (!res && app == Settings::mailApplication)
  {
    StringCchPrintf(tszCommandLine, MAX_COMMAND_LINE, DEFAULT_MAIL_APPLICATION_ARGS, account, to);
    CreateProcess(DEFAULT_MAIL_APPLICATION, tszCommandLine, 
      NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
  }
}

void PhoneUtils::OpenURL(const TCHAR* url)
{
  TCHAR fullUrl[1024];
  SHELLEXECUTEINFO sei = {0};
  sei.cbSize = sizeof(SHELLEXECUTEINFO);
  sei.fMask = SEE_MASK_NOCLOSEPROCESS;
  sei.hwnd = NULL;
  sei.lpVerb = TEXT("open");
  if (_tcsstr(url, TEXT("://")) != NULL)
  {
    sei.lpFile = url;
  }
  else
  {
    StringCchCopy(fullUrl, 1024, TEXT("http://"));
    StringCchCat(fullUrl, 1024, url);
    sei.lpFile = fullUrl;
  }
  sei.lpParameters = NULL;

  sei.lpDirectory = NULL;
  sei.nShow = SW_SHOW;
  sei.hInstApp = NULL; 

  ShellExecuteEx(&sei);
}

void PhoneUtils::RunDialer(bool defaultOnly)
{
    PROCESS_INFORMATION pi;

    const TCHAR* app = DEFAULT_DIALER_APPLICATION;
    if (!defaultOnly && Settings::dialerApplication && Settings::dialerApplication[0] != 0)
      app = Settings::dialerApplication;
    const TCHAR* args = DEFAULT_DIALER_APPLICATION_ARGS;
    if (!defaultOnly && Settings::dialerApplicationArgs && Settings::dialerApplicationArgs[0] != 0)
      args = Settings::dialerApplicationArgs;

    BOOL res = CreateProcess(app, args,
        NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);

    if (!res && !defaultOnly && app == Settings::dialerApplication)
    {
      CreateProcess(DEFAULT_DIALER_APPLICATION, DEFAULT_DIALER_APPLICATION_ARGS,
        NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
    }
}

bool PhoneUtils::RunOutlook()
{
  PROCESS_INFORMATION pi;
  BOOL res = CreateProcess(OUTLOOK_APPLICATION, OUTLOOK_APPLICATION_ARGS,
        NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
  return res == TRUE;
}

bool PhoneUtils::ResetMissedCalls()
{
  HRESULT hr = RegistrySetDWORD(
      SN_PHONEMISSEDCALLS_ROOT, 
      SN_PHONEMISSEDCALLS_PATH, 
      SN_PHONEMISSEDCALLS_VALUE,
      0);
  DWORD dwStatus;
  hr = RegistryGetDWORD(
      SN_PHONEMISSEDCALL_ROOT, 
      SN_PHONEMISSEDCALL_PATH, 
      SN_PHONEMISSEDCALL_VALUE,
      &dwStatus);
  if (SUCCEEDED(hr))
  {
    hr = RegistrySetDWORD(
        SN_PHONEMISSEDCALL_ROOT, 
        SN_PHONEMISSEDCALL_PATH, 
        SN_PHONEMISSEDCALL_VALUE,
        dwStatus & (0xFFFFFFFF ^ SN_PHONEMISSEDCALL_BITMASK));
  }
  return SUCCEEDED(hr);
}

#ifdef CALLVMAIL

void PhoneUtils::CallVmail() {
    TCHAR szVal[MAX_REG_SZ_CHARS];
    szVal[0] = 0;

    HRESULT success;

    success = RegistryGetString(HKEY_CURRENT_USER, 
        TEXT("Software\\Microsoft\\Vmail"), TEXT("PhoneNumber1"), szVal,
        MAX_REG_SZ_CHARS); 

    if (SUCCEEDED(success) && _tcslen(szVal) > 5) {
        Call(szVal, TEXT("Voicemail"));
        return;
    }

    success = RegistryGetString(HKEY_CURRENT_USER, 
        TEXT("Software\\Microsoft\\Vmail"), TEXT("UserProvidedNumber1"), szVal,
        MAX_REG_SZ_CHARS); 

    if (SUCCEEDED(success) && _tcslen(szVal) > 5) {
        Call(szVal, TEXT("Voicemail"));
        return;
    }

    success = SHGetPhoneNumber(szVal, MAX_REG_SZ_CHARS, 1);
    if (SUCCEEDED(success)) {
        Call(szVal, TEXT("Voicemail"));
        return;
    }
}

// utility functions
 
// http://blogs.msdn.com/windowsmobile/archive/2004/11/28/271110.aspx
HRESULT SHReadLineAddressCaps(LPTSTR szNumber, UINT cchNumber, PDWORD pdwCallFwdModes, UINT nLineNumber) {
    HRESULT  hr = E_FAIL;
    LRESULT  lResult = 0;
    HLINEAPP hLineApp;
    DWORD    dwNumDevs;
    DWORD    dwAPIVersion = TAPI_API_HIGH_VERSION;
    LINEINITIALIZEEXPARAMS liep;

    DWORD dwTAPILineDeviceID;
    const DWORD dwAddressID = nLineNumber - 1;

    liep.dwTotalSize = sizeof(liep);
    liep.dwOptions   = LINEINITIALIZEEXOPTION_USEEVENT;

    if (SUCCEEDED(lineInitializeEx(&hLineApp, 0, 0, TEXT("ExTapi_Lib"), &dwNumDevs, &dwAPIVersion, &liep))) {

        BYTE* pCapBuf = NULL;
        DWORD dwCapBufSize = CAPS_BUFFER_SIZE;
        LINEEXTENSIONID  LineExtensionID;
        LINEDEVCAPS*     pLineDevCaps = NULL;
        LINEADDRESSCAPS* placAddressCaps = NULL;

        pCapBuf = new BYTE[dwCapBufSize];
        EXIT_ON_NULL(pCapBuf);

        pLineDevCaps = (LINEDEVCAPS*)pCapBuf;
        pLineDevCaps->dwTotalSize = dwCapBufSize;

        // Get TSP Line Device ID
        dwTAPILineDeviceID = 0xffffffff;
        for (DWORD dwCurrentDevID = 0 ; dwCurrentDevID < dwNumDevs ; dwCurrentDevID++) {
            if (0 == lineNegotiateAPIVersion(hLineApp, dwCurrentDevID, TAPI_API_LOW_VERSION, TAPI_API_HIGH_VERSION,
                &dwAPIVersion, &LineExtensionID))
            {
                lResult = lineGetDevCaps(hLineApp, dwCurrentDevID, dwAPIVersion, 0, pLineDevCaps);

                if (dwCapBufSize < pLineDevCaps->dwNeededSize) {
                    delete[] pCapBuf;
                    dwCapBufSize = pLineDevCaps->dwNeededSize;
                    pCapBuf = new BYTE[dwCapBufSize];
                    EXIT_ON_NULL(pCapBuf);

                    pLineDevCaps = (LINEDEVCAPS*)pCapBuf;
                    pLineDevCaps->dwTotalSize = dwCapBufSize;

                    lResult = lineGetDevCaps(hLineApp, dwCurrentDevID, dwAPIVersion, 0, pLineDevCaps);
                }

                if ((0 == lResult) &&
                    (0 == _tcscmp((TCHAR*)((BYTE*)pLineDevCaps+pLineDevCaps->dwLineNameOffset), CELLTSP_LINENAME_STRING)))
                {
                    dwTAPILineDeviceID = dwCurrentDevID;
                    break;
                }
            }
        }

        placAddressCaps = (LINEADDRESSCAPS*)pCapBuf;
        placAddressCaps->dwTotalSize = dwCapBufSize;

        lResult = lineGetAddressCaps(hLineApp, dwTAPILineDeviceID, dwAddressID, dwAPIVersion, 0, placAddressCaps);

        if (dwCapBufSize < placAddressCaps->dwNeededSize) {
            delete[] pCapBuf;
            dwCapBufSize = placAddressCaps->dwNeededSize;
            pCapBuf = new BYTE[dwCapBufSize];
            EXIT_ON_NULL(pCapBuf);

            placAddressCaps = (LINEADDRESSCAPS*)pCapBuf;
            placAddressCaps->dwTotalSize = dwCapBufSize;

            lResult = lineGetAddressCaps(hLineApp, dwTAPILineDeviceID, dwAddressID, dwAPIVersion, 0, placAddressCaps);
        }

        if (0 == lResult) {
            if (szNumber) {
                szNumber[0] = TEXT('\0');

                EXIT_ON_FALSE(0 != placAddressCaps->dwAddressSize);

                // A non-zero dwAddressSize means a phone number was found
                ASSERT(0 != placAddressCaps->dwAddressOffset);
                PWCHAR tsAddress = (WCHAR*)(((BYTE*)placAddressCaps)+placAddressCaps->dwAddressOffset);

                StringCchCopy(szNumber, cchNumber, tsAddress);
            }

            // Record the allowed forwarding modes
            if (pdwCallFwdModes)
            {
                *pdwCallFwdModes = placAddressCaps->dwForwardModes;
            }

            hr = S_OK;
        }

        delete[] pCapBuf;
    } // End if ()

FuncExit:
    lineShutdown(hLineApp);

    return hr;
}

// szNumber - Out Buffer for the phone number
// cchNumber - size of sznumber in characters
// nLineNumber - which phone line (1 or 2) to get the number for
HRESULT SHGetPhoneNumber(LPTSTR szNumber, UINT cchNumber, UINT nLineNumber) {
    return SHReadLineAddressCaps(szNumber, cchNumber, NULL, nLineNumber);
}

#endif
